<?php
/**
 * Workflow executer that suspends and resumes workflow
 * execution states to and from a database.
 * @author dengdl
 *
 */
class Workflow_Execution_Database extends Workflow_Execution
{
/**
     * Zend_Db_Adapter_Abstract instance to be used.
     *
     * @var Zend_Db_Adapter_Abstract
     */
    protected $db;

    /**
     * Flag that indicates whether the execution has been loaded.
     *
     * @var boolean
     */
    protected $loaded = false;

    /**
     * Container to hold the properties
     *
     * @var array(string=>mixed)
     */
    protected $properties = array(
      'definitionStorage' => null,
      'workflow' => null      
    );

    /**
	 * @return the $db
	 */
	public function getDb() {
		return $this->db;
	}

	/**
	 * @param Zend_Db_Adapter_Abstract $db
	 */
	public function setDb($db) {
		$this->db = $db;
	}
	
	protected $logEvent = true;

	/**
     * Construct a new database execution.
     *
     * This constructor is a tie-in.
     *
     * @param  Zend_Db_Adapter_Abstract $db
     * @param  int          $executionId
     * @throws 
     */
    public function __construct ( Zend_Db_Adapter_Abstract $db, $executionId = null )
    {
        if ( $executionId !== null && !is_int( $executionId ) )
        {
            throw new Exception( '$executionId must be an integer.' );
        }

        $this->db = $db;
        $this->properties['definitionStorage'] = new Workflow_DefinitionStorage_Database( $db );
        

        if ( is_int( $executionId ) )
        {
            $this->loadExecution( $executionId );
        }
        
        if ($this->logEvent) 
        {
        	$writer = new Zend_Log_Writer_Stream(APPLICATION_PATH . '/workflowlog/log.txt');
			$logger = new Zend_Log($writer);
        	$this->addListener(new Workflow_Execution_EventLogListener($logger));
        }
        
        $this->addPlugin(new Workflow_Execution_DatabasePlugin);
    }

    

    /**
     * Property set access.
     *
     * @param string $propertyName
     * @param string $propertyValue
     * @throws ezcBasePropertyNotFoundException
     *         If the given property could not be found.
     * @throws ezcBaseValueException
     *         If the value for the property options is not an ezcWorkflowDatabaseOptions object.
     * @ignore
     */
    public function __set( $propertyName, $propertyValue )
    {
        switch ( $propertyName )
        {
            case 'definitionStorage':
            case 'workflow':
                return parent::__set( $propertyName, $propertyValue );            
        }
        $this->properties[$propertyName] = $propertyValue;
    }

    /**
     * Property isset access.
     *
     * @param string $propertyName
     * @return bool
     * @ignore
     */
    public function __isset( $propertyName )
    {
        switch ( $propertyName )
        {
            case 'definitionStorage':
            case 'workflow':           
                return true;
        }

        return false;
    }

    /**
     * Start workflow execution.
     *
     * @param  int $parentId
     * @throws 
     */
    protected function doStart( $parentId )
    {        
    	$this->db->beginTransaction();
    	
        $data = array(
        	'workflow_id' => $this->workflow->id,
        	'execution_parent' => $parentId,
        	'execution_started' => time(),
        	'execution_variables' => Workflow_DatabaseUtil::serialize( $this->variables ),
        	'execution_waiting_for' => Workflow_DatabaseUtil::serialize( $this->waitingFor ),
        	'execution_threads' => Workflow_DatabaseUtil::serialize( $this->threads ),
        	'execution_next_thread_id' => $this->nextThreadId
        	
        );
        
        $this->db->insert('execution', $data);        

        $this->id = $this->db->lastInsertId();
    }

    /**
     * Suspend workflow execution.
     *
     * @throws 
     */
    protected function doSuspend()
    {
        $this->cleanupTable( 'execution_state' );
        
        $this->db->update('execution', array(
        	'execution_suspended' => time(),
        	'execution_variables' => Workflow_DatabaseUtil::serialize( $this->variables ),
        	'execution_waiting_for' => Workflow_DatabaseUtil::serialize( $this->waitingFor ),
        	'execution_threads' => Workflow_DatabaseUtil::serialize( $this->threads ),
        	'execution_next_thread_id' => $this->nextThreadId
        ), array(
        	'execution_id = ?' => $this->id
        ));

        foreach ( $this->activatedNodes as $node )
        {
        	$this->db->insert('execution_state', array(
        		'execution_id' => $this->id,
        		'node_id' => $node->getId(),
        		'node_state' => Workflow_DatabaseUtil::serialize( $node->getState() ),
        		'node_activated_from' => Workflow_DatabaseUtil::serialize( $node->getActivatedFrom() ),
        		'node_thread_id' => $node->getThreadId()
        	));
        }
         
        $this->db->commit();       
    }

    /**
     * Resume workflow execution.
     *
     * @throws ezcDbException
     */
    protected function doResume()
    {
        $this->db->beginTransaction();
    }

    /**
     * End workflow execution.
     *
     * @throws ezcDbException
     */
    protected function doEnd()
    {
        $this->cleanupTable( 'execution' );
        $this->cleanupTable( 'execution_state' );
        
        $this->cleanupTable( 'execution_usertask' );
        $this->cleanupTable( 'execution_workitem_candidates' );

        if ( !$this->isCancelled() )
        {
            $this->db->commit();
        }
    }

    /**
     * Returns a new execution object for a sub workflow.
     *
     * @param  int $id
     * @return ezcWorkflowExecution
     */
    protected function doGetSubExecution( $id = null )
    {
        return new Workflow_Execution_Database( $this->db, $id );
    }

    /**
     * Cleanup execution / execution_state tables.
     *
     * @param  string $tableName
     * @throws ezcDbException
     */
    protected function cleanupTable( $tableName )
    {
    	if ( $tableName == 'execution' )
        {
        	$this->db->query('delete from execution where execution_id = :id or execution_parent = :id', array('id' => $this->id));
        }
    	else 
    	{
    		$this->db->delete($tableName, array('execution_id = ?' => $this->id));	
    	}        
    } 
    

    /**
     * Load execution state.
     *
     * @param int $executionId  ID of the execution to load.
     * @throws 
     */
    protected function loadExecution( $executionId )
    {
    	$result = $this->db->fetchAll("select *	from execution where execution_id = ?", $executionId); 
//    	$result = $this->db->fetchAll("select workflow_id
//    					, execution_variables
//    					, execution_threads
//    					, execution_next_thread_id
//    					, execution_waiting_for
//    					from execution where execution_id = ?", $executionId);   	
        

        if ( $result === false || empty( $result ) )
        {
            throw new Exception(
              'Could not load execution state.'
            );
        }

        $this->id = $executionId;
        $this->nextThreadId = $result[0]['execution_next_thread_id'];

        $this->threads = Workflow_DatabaseUtil::unserialize( $result[0]['execution_threads'] );
        $this->variables = Workflow_DatabaseUtil::unserialize( $result[0]['execution_variables'] );
        $this->waitingFor = Workflow_DatabaseUtil::unserialize( $result[0]['execution_waiting_for'] );

        $workflowId     = $result[0]['workflow_id'];
        $this->workflow = $this->properties['definitionStorage']->loadById( $workflowId );

        $result = $this->db->fetchAll("select node_id
        							, node_state
        							, node_activated_from
        							, node_thread_id
        							from execution_state where execution_id = ?", $executionId);        
        
        $active = array();

        foreach ( $result as $row )
        {
            $active[$row['node_id']] = array(
              'activated_from' => Workflow_DatabaseUtil::unserialize(
                $row['node_activated_from']
              ),
              'state' => Workflow_DatabaseUtil::unserialize(
                $row['node_state'], null
              ),
              'thread_id' => $row['node_thread_id']
            );
        }

        foreach ( $this->workflow->nodes as $node )
        {
            $nodeId = $node->getId();

            if ( isset( $active[$nodeId] ) )
            {
                $node->setActivationState( Workflow_Node::WAITING_FOR_EXECUTION );
                $node->setThreadId( $active[$nodeId]['thread_id'] );
                $node->setState( $active[$nodeId]['state'], null );
                $node->setActivatedFrom( $active[$nodeId]['activated_from'] );

                $this->activate( $node, false );
            }
        }

        $this->cancelled = false;
        $this->ended     = false;
        $this->loaded    = true;
        $this->resumed   = false;
        $this->suspended = true;
    }
}
?>